-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Auto detect and merge lockfile conflicts #3544
Conversation
src/lockfile/parse.js
Outdated
@@ -314,9 +314,80 @@ export class Parser { | |||
} | |||
} | |||
|
|||
export default function(str: string, fileLoc: string = 'lockfile'): Object { | |||
str = stripBOM(str); | |||
const GIT_MERGE_CONFLICT_START = '<<<<<<<'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Drop the "GIT_", other tools will generate similar conflict markers.
afffeb6
to
37116f4
Compare
src/lockfile/parse.js
Outdated
let variantTwo = []; | ||
while (lines.length) { | ||
const line = lines.shift(); | ||
if (line.startsWith(MERGE_CONFLCIT_END)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo
src/lockfile/parse.js
Outdated
if (line === MERGE_CONFLICT_SEP) { | ||
break; | ||
} else { | ||
variantOne.push(line); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can't you just push directly into variants[0]
? or at least use let variantOne = variants[0]
so that the concat becomes unneeded
src/lockfile/parse.js
Outdated
variants[0] = variants[0].concat(variantOne); | ||
|
||
// get the second variant | ||
let variantTwo = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above
37116f4
to
b0c3929
Compare
Addressed nits, thanks @arcanis! |
src/lockfile/parse.js
Outdated
// get the first variant | ||
while (lines.length) { | ||
const line = lines.shift(); | ||
if (line === MERGE_CONFLICT_SEP) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feature looks awesome.
Curious if you all are wanting to support git's diff3
merge conflict style (https://git-scm.com/docs/git-merge#_how_conflicts_are_presented). I think this code will break for users using diff3
because the first variant would include both the real variant and the common parent ancestor of both conflicts. I wouldn't mind making a PR in the future for supporting diff3
.
Sorry if I'm overlooking something.
src/lockfile/parse.js
Outdated
*/ | ||
|
||
function parseWithConflict(str: string, fileLoc: string): ParseResult { | ||
const variants = extractConflictVariants(str); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We assume here that only 1 merge conflict exists. We could handle 2+ conflicts if call parseWithConflict
recursively.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It supports multiple conflicts in the same file, but during a same conflict pass. I think it should be good enough for a first iteration :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@arcanis cool, would be nice to add an extra test for in that case.
__tests__/lockfile.js
Outdated
@@ -193,3 +193,44 @@ test('Lockfile.getLockfile (sorting)', () => { | |||
|
|||
expect(actual).toEqual(expected); | |||
}); | |||
|
|||
test('parse merge conflicts', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
better name -> 'parse single merge conflict'
@kittens neat! Could you please add git diff3 conflictstyle support? |
bf5d212
to
29b549f
Compare
29b549f
to
1ca53f3
Compare
Rebased, fixed up the tests, made flow more strict. Added support for diff3 by discarding the common ancestor from the parsed result. It seems to me like the information shown is purely for humans to make better assumptions about what to do in case of a conflict, which doesn't apply here. Let me know if that is wrong. Anything else needed here to get this merged? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall LGTM.
One question: should we make this behavior opt-in via a --merge
flag?
@@ -193,3 +193,101 @@ test('Lockfile.getLockfile (sorting)', () => { | |||
|
|||
expect(actual).toEqual(expected); | |||
}); | |||
|
|||
test('parse single merge conflict', () => { | |||
const file = ` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shall we not put these into a separate file? I can see both ways so just raising the question, not strong opinions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's much easier to manage it like this, and we care more about the text input than we care about where it is coming from for the purpose of this test.
__tests__/lockfile.js
Outdated
|
||
const {type, object} = parse(file); | ||
expect(type).toEqual('merge'); | ||
expect(object.a.no).toEqual('yes'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not
expect(object).toEqual({
a: { no: 'yes' },
b: { foo: 'bar' },
c: { bar: 'foo' },
d: { yes: 'no' },
});
<<<<<<< HEAD | ||
b: | ||
foo: "bar" | ||
======= |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not try adding both, which is most probably what is needed when installing packages?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, it might not be obvious but in this example, the format is actually wrong because yarn lockfiles don't support colons :
, which is why it results in a conflict.
src/lockfile/parse.js
Outdated
|
||
export function extractConflictVariants(str: string): Array<string> { | ||
const variants: Array<Array<string>> = [[], []]; | ||
const lines = str.split(/\n/g); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this safe or should we do [\r\n]
instead?
src/lockfile/parse.js
Outdated
if (line.startsWith(MERGE_CONFLICT_START)) { | ||
// get the first variant | ||
while (lines.length) { | ||
const line = lines.shift(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better to not mask the outer variable?
src/lockfile/parse.js
Outdated
* Check if a lockfile has merge conflicts. | ||
*/ | ||
function hasMergeConflicts(str: string): boolean { | ||
return str.includes(MERGE_CONFLICT_START); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be better to check if all 3 markers exist?
1ca53f3
to
2c8a0b2
Compare
I just tried this on Jest jestjs/jest#3906 and it worked flawlessly, this feature is so awesome. @kittens thanks for the idea and getting us 99% there! |
**Summary** Follow up to #3544. Currently, `yarn` happily keeps moving if it detects merge conflicts and is able to resolve them in the lockfile. That said it doesn't persist the resolution by saving the lockfile to disk again. This patch ensures writing the lockfile if it is "dirty". This patch also causes `yarn` to throw an error if there are merge conflicts in the file and `--frozen-lockfile` option is true. **Test plan** Existing unit tests. Also try running `yarn install` with the following files: `package.json` ``` { "name": "yarnlock-auto-merge", "version": "1.0.0", "main": "index.js", "author": "Burak Yigit Kaya <byk@fb.com>", "license": "MIT", "dependencies": { "left-pad": "^1.1.3", "right-pad": "^1.0.1" } } ``` `yarn.lock` ``` <<<<<<< HEAD left-pad@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.1.3.tgz#612f61c033f3a9e08e939f1caebeea41b6f3199a" ======= right-pad@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/right-pad/-/right-pad-1.0.1.tgz#8ca08c2cbb5b55e74dafa96bf7fd1a27d568c8d0" >>>>>>> right-pad ``` Without the patch, `yarn` won't update the lockfile. With the patch, the lockfile is replaced with the merged version.
) **Summary** Follow up to #3544. Currently, `yarn` happily keeps moving if it detects merge conflicts and is able to resolve them in the lockfile. That said it doesn't persist the resolution by saving the lockfile to disk again. This patch ensures writing the lockfile if it is "dirty". This patch also causes `yarn` to throw an error if there are merge conflicts in the file and `--frozen-lockfile` option is true. **Test plan** Existing unit tests. Also try running `yarn install` with the following files: `package.json` ``` { "name": "yarnlock-auto-merge", "version": "1.0.0", "main": "index.js", "author": "Burak Yigit Kaya <byk@fb.com>", "license": "MIT", "dependencies": { "left-pad": "^1.1.3", "right-pad": "^1.0.1" } } ``` `yarn.lock` ``` <<<<<<< HEAD left-pad@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.1.3.tgz#612f61c033f3a9e08e939f1caebeea41b6f3199a" ======= right-pad@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/right-pad/-/right-pad-1.0.1.tgz#8ca08c2cbb5b55e74dafa96bf7fd1a27d568c8d0" >>>>>>> right-pad ``` Without the patch, `yarn` won't update the lockfile. With the patch, the lockfile is replaced with the merged version.
@cpojer, it's a bad habit to do this: |
Would be nice to update the |
What are implications of using |
@Vanuan I expect that to not change the file at all but use the merged version in-memory. |
So how do I resolve conflicts without upgrading packages? Only by hand? |
@Vanuan I don't really follow that reasoning. |
Hm. Somehow I was under impression that it would. I've seen 3 situations where yarn.lock ends up changed:
The 3rd situation is mitigated by this PR. But it isn't clear whether conflicted packages would be resolved again potentially upgrading the version of unrelated packages. Well, it looks like
In this case |
The "upgrade" i'm referring to is when
So the question is whether package version gets resolved again in case of merge conflicts. |
@Vanuan it is quite hard to keep track of what needs to be done under a closed PR. Would you mind summarizing this in a new issue as a feature request or a bug so it is clear to anyone outside of this PR what is asked to be done. This way it would be asier to reason about and work on it. |
Thanks for you patience. This probably doesn't belong here on GitHub. |
I'm locking this. The conversation that evolved isn't related to the original issue, if you have concerns about this issue, please send a pull request to fix it. Thanks! |
Summary
Git merge conflicts are common with
yarn.lock
. This PR allows you to runyarn
to automatically merge the two versions ofyarn.lock
which will run Yarn and regenerate a new lockfile.Test plan
Added tests.